package com.company.nuwa.common.utils;

import java.util.Random;

/**
 * <p>红包算法工具类</p>
 * 参考：https://blog.csdn.net/paincupid/article/details/82054647
 * @author Lionel Lee
 * @version 1.0: RedPacketTools.java
 * @date 2021/5/31 5:04 下午
 */
public class RedPacketTools {

    private static final Random random = new Random();

    /**
     * 返回一次抽奖在指定中奖概率下是否中奖
     * @param rate 中奖概率
     * @return xxx
     */
    private static boolean canReward(double rate) {
        return Math.random() <= rate;
    }

    /**
     * 返回min~max区间内随机数，含min和max
     * @param min xxx
     * @param max xxx
     * @return xxx
     */
    private static int getRandomVal(int min, int max) {
        return random.nextInt(Math.abs(max - min + 1)) + min;
    }

    /**
     * 带概率偏向的随机算法，概率偏向subMin~subMax区间
     * 返回boundMin~boundMax区间内随机数（含boundMin和boundMax），同时可以指定子区间subMin~subMax的优先概率
     * 例：传入参数(10, 50, 20, 30, 0.8)，则随机结果有80%概率从20~30中随机返回，有20%概率从10~50中随机返回
     * @param boundMin 边界
     * @param boundMax xxx
     * @param subMin xxx
     * @param subMax xxx
     * @param subRate xxx
     * @return xxx
     */
    private static int getRandomValWithSpecifySubRate(int boundMin, int boundMax, int subMin,
                                                      int subMax, double subRate) {
        if (canReward(subRate)) {
            return getRandomVal(subMin, subMax);
        }
        return getRandomVal(boundMin, boundMax);
    }

    /**
     * 随机分配第n个红包
     * @param totalBonus 总红包量
     * @param totalNum 总份数
     * @param sendedBonus 已发送红包量
     * @param sendedNum 已发送份数
     * @param rdMin 随机下限
     * @param rdMax 随机上限
     * @return xxx
     */
    private static int randomBonusWithSpecifyBound(int totalBonus, int totalNum, int sendedBonus,
                                                   int sendedNum, int rdMin, int rdMax,
                                                   double bigRate) {
        double avg = totalBonus / (double) totalNum; // 平均值
        int leftLen = (int) Math.ceil(avg - rdMin);
        int rightLen = (int) Math.floor(rdMax - avg);
        int boundMin = 0;
        int boundMax = 0;

        // 大范围设置小概率
        if (leftLen == rightLen) {
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMax),
                rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMin),
                rdMax);
        } else if (rightLen - leftLen > 0) {
            // 上限偏离
            int standardRdMax = (int) Math.ceil(avg + leftLen); // 右侧对称上限点
            int _rdMax = canReward(bigRate) ? rdMax : standardRdMax;
            boundMin = Math.max(
                (totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMax), rdMin);
            boundMax = Math.min((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMin),
                _rdMax);
        } else {
            // 下限偏离
            int standardRdMin = (int) Math.floor(avg - rightLen); // 左侧对称下限点
            int _rdMin = canReward(bigRate) ? rdMin : standardRdMin;
            boundMin = Math.max((totalBonus - sendedBonus - (totalNum - sendedNum - 1) * rdMax),
                _rdMin);
            boundMax = Math.min(
                (totalBonus - sendedBonus - (totalNum - sendedNum - 1) * standardRdMin), rdMax);
        }

        // 已发平均值偏移修正-动态比例
        if (boundMin == boundMax) {
            return getRandomVal(boundMin, boundMax);
        }
        double currAvg = sendedNum == 0 ? avg : (sendedBonus / (double) sendedNum); // 当前已发平均值
        double middle = (boundMin + boundMax) / 2.0;
        int subMin = boundMin, subMax = boundMax;
        // 期望值
        double exp = avg - (currAvg - avg) * sendedNum / (double) (totalNum - sendedNum);
        if (middle > exp) {
            subMax = (int) Math.round((boundMin + exp) / 2.0);
        } else {
            subMin = (int) Math.round((exp + boundMax) / 2.0);
        }
        int expBound = (boundMin + boundMax) / 2;
        int expSub = (subMin + subMax) / 2;
        double subRate = (exp - expBound) / (double) (expSub - expBound);
        return getRandomValWithSpecifySubRate(boundMin, boundMax, subMin, subMax, subRate);
    }

    /**
     * 生成红包一次分配结果
     * @param totalBonus xxx
     * @param totalNum xxx
     * @param rdMin xxx
     * @param rdMax xxx
     * @param bigRate 指定大范围区间的概率
     * @return xxx
     */
    public static int[] createBonusList(int totalBonus, int totalNum, int rdMin, int rdMax,
                                        double bigRate) {
        int sendedBonus = 0;
        int sendedNum = 0;
        int[] bonusList = new int[totalNum];
        while (sendedNum < totalNum) {
            int bonus = randomBonusWithSpecifyBound(totalBonus, totalNum, sendedBonus, sendedNum,
                rdMin, rdMax, bigRate);
            bonusList[sendedNum] = bonus;
            sendedNum++;
            sendedBonus += bonus;
        }
        return bonusList;
    }

}
